// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

#include <fstream>

#include "NodeCast.h"
#include "Rules/Rule.h"
#include "FileContent.h"

#include "UpdateInstance.h"

#include <Rules/RuleList.h>

using namespace CangjieMigrator;
using namespace CangjieMigrator::UpdateInstance;

File::File(const Path &path) : path(path), content(std::make_unique<FileContentLine>(path))
{}

Path File::GetPath() const
{
    return path;
}

void File::Add(const Actions::Action action)
{
    Dispatch(action,
        [this](const Actions::Replace &replace) {
            Add(replace);
        },
        [this](const Actions::Comment &comment) {
            Add(comment);
        });
}

void File::Add(Actions::Replace replace)
{
    for (auto it = replaceList.begin(); it != replaceList.end();) {
        if (replace.range.IsInclude(it->range)) {
            deprecatedReplaceList.push_back(std::move(*it));
            it = replaceList.erase(it);
        } else {
            ++it;
        }
    }
    replaceList.emplace_back(std::move(replace));
}

void File::Add(Actions::Comment comment)
{
    commentList.emplace_back(comment);
}

void File::Add(const Cangjie::AST::PackageSpec &node, const PositionRange &range)
{
    importInfo.Add(node, range);
}

void File::Add(const Cangjie::AST::ImportSpec &node, const PositionRange &range)
{
    importInfo.Add(node, range);
}

void File::Add(const Rules::Rule *rule)
{
    matchImportRules.insert(&rule->importRule);
}

void File::ChangeImport()
{
    importInfo.Change(matchImportRules);
    importInfo.DirectReplaceChange();
}

bool File::NeedImportStdx() const
{
    return importInfo.NeedImportStdx();
}

bool File::Generate(const Path &target) const
{
    const auto importResult = importInfo.Generate();

    auto actions = std::vector<Actions::Action>();
    actions.reserve(commentList.size() + replaceList.size());
    for (const auto &comment : commentList) {
        actions.emplace_back(comment);
    }
    for (const auto &replace : replaceList) {
        actions.emplace_back(replace);
    }
    content->Apply(std::move(actions));

    content->Apply(importResult);

    if (!content->WriteTo(target)) {
        std::cerr << "Write to file " << target << " failed when generate from " << path << "." << std::endl;
        return false;
    }

    return true;
}

std::string File::GetContentInRange(const PositionRange &range)
{
    // Pre-reserve capacity to avoid reallocation, which would otherwise invalidate pointers stored in includedResult.
    includedReplaceList.reserve(includedReplaceList.size() + replaceList.size());
    // All pointers in 'includedResult' point to elements stored in 'includedReplaceList'.
    std::vector<const Actions::Replace *> includedResult;
    for (auto it = replaceList.begin(); it != replaceList.end();) {
        if (range.IsInclude(it->range)) {
            includedReplaceList.push_back(std::move(*it));
            includedResult.push_back(&includedReplaceList.back());

            it = replaceList.erase(it);
        } else {
            ++it;
        }
    }
    for (const auto &item : includedReplaceList) {
        if (!range.IsInclude(item.range)) {
            continue;
        }

        bool isContainedByExisting = false;
        for (const auto *existing : includedResult) {
            if (existing->range.IsInclude(item.range)) {
                isContainedByExisting = true;
                break;
            }
        }

        if (!isContainedByExisting) {
            includedResult.push_back(&item);
        }
    }

    if (includedResult.empty()) {
        return (*content)[range];
    }

    auto [currentPos, endPos] = content->GetStringRange(range);

    std::string result;
    for (const auto *m : includedResult) {
        auto [rangeStart, rangeEnd] = content->GetStringRange(m->range);

        if (currentPos < rangeStart) {
            result += content->SubStr(currentPos, rangeStart - currentPos);
        }

        result += m->update;
        currentPos = rangeEnd;
    }

    if (currentPos < endPos) {
        result += content->SubStr(currentPos, endPos - currentPos);
    }

    return result;
}

Package::Package(Path path) : path(std::move(path))
{}

Package::Package(const PackageCompileInfo &info) : path(info.packagePath)
{
    importPaths.insert(importPaths.end(), info.importPath.begin(), info.importPath.end());
}

const Path &Package::GetPath() const
{
    return path;
}

const std::vector<Path> &Package::GetImportPaths() const
{
    return importPaths;
}

void Package::AddImportPath(Path importPath)
{
    importPaths.emplace_back(std::move(importPath));
}

File &Package::GetFile(const std::string &fileName)
{
    if (files.find(fileName) == files.end()) {
        files[fileName] = std::make_unique<File>(path / fileName);
    }
    return *files[fileName];
}

void Package::ChangeImport()
{
    for (const auto &[name, file] : files) {
        file->ChangeImport();
    }
}

bool Package::NeedImportStdx() const
{
    auto needImportStdx = false;
    for (const auto &[name, file] : files) {
        if (!file) {
            continue;
        }
        needImportStdx |= file->NeedImportStdx();
    }
    return needImportStdx;
}

bool Package::Generate(const Path &target)
{
    bool result = true;
    for (const auto &[name, file] : files) {
        if (!file->Generate(target / name)) {
            std::cerr << "Generate package file " << name << " failed." << std::endl;
            result = false;
        }
    }
    return result;
}

Project::Project(Path path) : path(std::move(path))
{}

void Project::SetPackages(const std::vector<PackageCompileInfo> &infos)
{
    for (const auto &info : infos) {
        packages.push_back(std::move(std::make_unique<Package>(info)));
    }
}

std::vector<std::unique_ptr<Package>> &Project::GetPackages()
{
    return packages;
}

bool Project::NeedImportStdx() const
{
    auto needImportStdx = false;
    for (const auto &package : packages) {
        if (!package) {
            continue;
        }
        needImportStdx |= package->NeedImportStdx();
    }
    return needImportStdx;
}
